package core;

import java.util.*;
import common.*;
import simulator.*;

public class Router extends Thread{
	public Hashtable<NodeRecord, Node> routing_table;
	public Hashtable<Integer, Long> pid_ip_map;
	private ArrayList<Integer> ip_pool;
	private Long system_up_time;
	private boolean b_firstnode = true;
	private long ip = Def.ROUTER_IP;	// always 192.168.0.1	//3232235521L;	
	
	private long master_ip;
	private int master_pid;
	private boolean running;
	
	private Analyzer dr_watson = null;
	
	public Router(){
		routing_table = new Hashtable<NodeRecord, Node>();
		pid_ip_map = new Hashtable<Integer, Long>();
		ip_pool = new ArrayList<Integer>();
		
		// generates available IP pool (prevent duplicate IP's are generated by rand()).
		for( int i = 0; i < Conf.MAX_NODES; i++ ){
			while( true ){
				Integer gen = ( new Random() ).nextInt( Conf.MAX_NODES * 2 ) + 2;
				
				if( !ip_pool.contains( gen ) ){
					ip_pool.add( gen );
					break;
				}
			}
		}
		
		// initial UTC time receiving.
		system_up_time = System.currentTimeMillis();
		
		running = true;
	}
	
	public void run(){	// periodically prints the current time
		int i = 0;

		while( running ){
			//print_log( "Uptime = " + i + " sec: Current time = " + System.currentTimeMillis() + " (ms)" );
			
			i++;
			
//			if( Conf.REPORT )
//				reporting( i );
			
			try{
				sleep( Conf.WATCHDOG_INTERVAL );	// 10 min
			}catch( InterruptedException e ){
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * send(): delivers a Message to the receiver
	 * @param outgoing_msg
	 * @return #msg_sent
	 */
	public int send( Message outgoing_msg ){
		// send msg
		if( outgoing_msg.get_receiver_pid() == Def.BROADCAST ){	// broadcasting
			Collection<Node> all_nodes = routing_table.values();
			Iterator<Node> itr = all_nodes.iterator();
			int br_count = 0;	// #msg broadcasted
			while( itr.hasNext() ){
				Node br_rcv = itr.next();	// br_rcv = broadcast, receiver
				br_rcv.msg_q.offer( outgoing_msg );
				br_count++;
			}
			return br_count;
			
		}else if( outgoing_msg.get_receiver_pid() == Def.SIMULATOR ){	// simulation results
			if( dr_watson != null ){
				dr_watson.msg_q.offer( outgoing_msg );
				return 1;
			}
			return 0;
			
		}else{	// 1-to-1 comm
			Node receiver = routing_table.get( new NodeRecord( outgoing_msg.get_receiver_pid(), outgoing_msg.get_receiver_ip() ) );
			if( receiver == null )
				return 0;
			
			receiver.msg_q.offer( outgoing_msg );
			return 1;
		}
	}
	
	public long ip_lookup( int pid ){
		return pid_ip_map.get( pid );
	}
	
	/**
	 * connect(): allows nodes to connect to the network
	 * @return newIp = ip addr assigned to the node, or -1 for err
	 */
	public synchronized long connect( int new_pid, Node new_node ){	// --- should be in CS
		long new_ip = -1;
		
		if( ip_pool.isEmpty() ){
			print_log( "no IP is available. The network is full.", Def.STDERR );
			return new_ip;
		}
		
		new_ip = ip_pool.remove( 0 );
		new_ip += 3232235520L;	// 192.168.0.x
		
		routing_table.put( new NodeRecord( new_pid, new_ip ), new_node ); //newMacAddr ) );
		if( pid_ip_map.put( new_pid, new_ip ) != null ){
			print_log( "this version doesn't support the pid duplicates", Def.STDERR );
		}
		
		// only the first node gets NTP time as the initial value
		if( b_firstnode ){
			new_node.set_master();		// turn on its interface
			this.set_master( new_ip, new_pid );
			b_firstnode = false;
		}else{
			new_node.set_master( this.master_pid, this.master_ip );
		}
		
		return new_ip;
	}
	
	public synchronized boolean disconnect( long ip, int pid ){	// --- should be in CS
		int old_ip = ( int )( ip - 3232235520L );	// 192.168.0.x
		
		if( ip_pool.contains( old_ip ) ){
			print_log( "IP " + ip + " has been already released.", Def.STDERR );
			return false;
		}
		
		if( ip_pool.add( old_ip ) ){
			routing_table.remove( new NodeRecord( pid, ip ) );
			pid_ip_map.remove( pid );
			print_log( "IP " + ip + " has been successfully released." );
			return true;
		}else
			print_log( "IP " + ip + " cannot be released.", Def.STDERR );
		
		return false;
	}
	
	public void set_master( long ip, int pid ){
		master_ip = ip;
		master_pid = pid;
	}
	
	public void add_analyzer( Analyzer _dr ){
		dr_watson = _dr;
	}
	
	// msg dump
	public void print_log( String str ){
		Utils.print_log( "-router(na:" + Utils.ipToString( ip ) + "): " + str );
	}
	public void print_log( String str, byte target ){
		Utils.print_log( "-router(na:" + Utils.ipToString( ip ) + "): " + str, target );
	}
}
